package org.vison.wonfu.framework.commons.tools.debounce;

import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

@Aspect
@Component
public class DebounceAspect {

    private final RedissonClient redissonClient;
    private final ExpressionParser parser = new SpelExpressionParser();
    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    @Pointcut("@annotation(org.vison.wonfu.framework.commons.tools.debounce.Debounce)")
    private void pointCut(){

    }

    @Autowired
    public DebounceAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 获取注解
        Debounce debounce = method.getAnnotation(Debounce.class);

        // 生成唯一的key
        String key = generateKey(joinPoint, debounce);

        // 获取Redisson锁
        RLock lock = redissonClient.getLock("debounce:" + key);

        try {
            // 尝试获取锁，如果获取成功则执行，否则说明在防抖时间内
            boolean locked = lock.tryLock(0, debounce.value(), debounce.unit());
            if (locked) {
                // 获取锁成功，执行原方法
                return joinPoint.proceed();
            } else {
                // 获取锁失败，说明在防抖时间内，抛出异常
                throw new DebounceException(debounce.message());
            }
        } finally {
            // 只有持有锁的线程才能释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 生成防抖的唯一key
     */
    @SneakyThrows
    private String generateKey(ProceedingJoinPoint joinPoint, Debounce debounce) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 基础key：类名+方法名
        StringBuilder keyBuilder = new StringBuilder();
        keyBuilder.append(method.getDeclaringClass().getName())
                .append(".")
                .append(method.getName());

        // 如果有自定义key，则解析SpEL表达式并添加
        if (!"".equals(debounce.key().trim())) {
            Object[] args = joinPoint.getArgs();
            EvaluationContext context = new StandardEvaluationContext();
            String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
            Object target = joinPoint.getTarget();
            // 获取所有成员属性注入到上下文
            for (Field field : target.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                context.setVariable(field.getName(), field.get(target));
            }
            for (int i = 0; i < parameterNames.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }

            String spELKey = parser.parseExpression(debounce.key()).getValue(context, String.class);
            keyBuilder.append(":").append(spELKey);
        }

        return keyBuilder.toString();
    }
}
